package ru.batrdmi.svnplugin.actions;

import com.intellij.history.LocalHistory;
import com.intellij.history.LocalHistoryAction;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsHistoryUtil;
import com.intellij.openapi.vcs.ui.ReplaceFileConfirmationDialog;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import ru.batrdmi.svnplugin.SVNRevisionGraph;
import ru.batrdmi.svnplugin.logic.Revision;

import java.io.FileOutputStream;
import java.io.IOException;

// This class is 'mostly' copied from com.intellij.openapi.vcs.history.FileHistoryPanelImpl
public class MyGetVersionAction extends AnAction {
    private static final Logger log = Logger.getInstance("ru.batrdmi.svnplugin.actions.MyGetVersionAction");

    public MyGetVersionAction() {
        super("Get", "Get version from repository", IconLoader.getIcon("/actions/get.png"));
    }

    public void update(AnActionEvent e) {
        Presentation presentation = e.getPresentation();
        FilePath filePath = e.getData(SVNRevisionGraph.SRC_FILE);
        Revision revision = e.getData(SVNRevisionGraph.SELECTED_REVISION);
        boolean enabled = isEnabled(filePath, revision);
        presentation.setEnabled(enabled);
        presentation.setVisible(enabled || !ActionPlaces.isPopupPlace(e.getPlace()));
    }

    public boolean isEnabled(FilePath filePath, Revision revision) {
        return filePath!= null && filePath.getVirtualFileParent() != null && !filePath.isDirectory()
                && revision != null && !revision.isDeleted();
    }

    @Override
    public void actionPerformed(AnActionEvent ae) {
        Revision rev = ae.getData(SVNRevisionGraph.SELECTED_REVISION);
        FilePath filePath = ae.getData(SVNRevisionGraph.SRC_FILE);
        Project project = ae.getData(PlatformDataKeys.PROJECT);
        VcsFileRevision revision = ae.getData(VcsDataKeys.VCS_FILE_REVISION);
        if (!isEnabled(filePath, rev)) {
            return;
        }

        if (ChangeListManager.getInstance(project).isFreezedWithNotification(null)) {
            return;
        }
        //noinspection ConstantConditions
        if (filePath.getVirtualFile() != null) {
            if (!new ReplaceFileConfirmationDialog(project, "Get revision")
                    .confirmFor(new VirtualFile[]{filePath.getVirtualFile()})) {
                return;
            }
        }
        getVersion(project, filePath, revision);
        refreshFile(project, filePath, revision);
    }

    private void refreshFile(Project project, final FilePath filePath, VcsFileRevision revision) {
        Runnable refresh = null;
        final VirtualFile vf = filePath.getVirtualFile();
        if (vf == null) {
            final LocalHistoryAction action = startLocalHistoryAction(filePath, revision);
            final VirtualFile vp = filePath.getVirtualFileParent();
            if (vp != null) {
                refresh = new Runnable() {
                    public void run() {
                        vp.refresh(false, true, new Runnable() {
                            public void run() {
                                filePath.refresh();
                                action.finish();
                            }
                        });
                    }
                };
            }
        } else {
            refresh = new Runnable() {
                public void run() {
                    vf.refresh(false, false);
                }
            };
        }
        if (refresh != null) {
            ProgressManager.getInstance().runProcessWithProgressSynchronously(refresh, "Refreshing files...", false, project);
        }
    }

    private void getVersion(final Project project, final FilePath filePath, final VcsFileRevision revision) {
        final VirtualFile file = filePath.getVirtualFile();
        if ((file != null) && !file.isWritable()) {
            if (ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(file).hasReadonlyFiles()) {
                return;
            }
        }

        new Task.Backgroundable(project, "Loading content...") {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                LocalHistoryAction action = file != null ? startLocalHistoryAction(filePath, revision) : LocalHistoryAction.NULL;
                final byte[] revisionContent;
                try {
                    revisionContent = VcsHistoryUtil.loadRevisionContent(revision);
                } catch (final IOException e) {
                    log.info(e);
                    ApplicationManager.getApplication().invokeLater(new Runnable() {
                        @Override public void run() {
                            Messages.showMessageDialog("Cannot load revision: " + e.getLocalizedMessage(),
                                    "Get Revision Content", Messages.getInformationIcon());
                        }
                    });
                    return;
                } catch (final VcsException e) {
                    log.info(e);
                    ApplicationManager.getApplication().invokeLater(new Runnable() {
                        @Override public void run() {
                            Messages.showMessageDialog("Cannot load revision: " + e.getLocalizedMessage(),
                                    "Get Revision Content", Messages.getInformationIcon());
                        }
                    });
                    return;
                } catch (ProcessCanceledException ex) {
                    return;
                }

                try {
                    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
                        @Override
                        public void run() {
                            ApplicationManager.getApplication().runWriteAction(new Runnable() {
                                public void run() {
                                    CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                                        public void run() {
                                            try {
                                                write(project, filePath, revisionContent);
                                            } catch (IOException e) {
                                                Messages.showMessageDialog("Cannot save content: " + e.getLocalizedMessage(),
                                                        "Get Revision Content", Messages.getErrorIcon());
                                            }
                                        }
                                    }, createGetActionTitle(filePath, revision), null);
                                }
                            });
                        }
                    });

                    if (file != null) {
                        VcsDirtyScopeManager.getInstance(project).fileDirty(file);
                    }
                } finally {
                    action.finish();
                }
            }
        }.queue();
    }

    private LocalHistoryAction startLocalHistoryAction(FilePath filePath, final VcsFileRevision revision) {
        return LocalHistory.getInstance().startAction(createGetActionTitle(filePath, revision));
    }

    private String createGetActionTitle(FilePath filePath, final VcsFileRevision revision) {
        return filePath.getIOFile().getAbsolutePath() + ": Get Version " + revision.getRevisionNumber();
    }

    private void write(Project project, FilePath filePath, byte[] revision) throws IOException {
        if (filePath.getVirtualFile() == null) {
            writeContentToIOFile(filePath, revision);
        } else {
            Document document = filePath.getDocument();
            if (document == null) {
                writeContentToFile(filePath, revision);
            } else {
                writeContentToDocument(project, filePath, document, revision);
            }
        }
    }

    private void writeContentToIOFile(FilePath filePath, byte[] revisionContent) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(filePath.getIOFile());
        try {
            outputStream.write(revisionContent);
        } finally {
            outputStream.close();
        }
    }

    private void writeContentToFile(FilePath filePath, final byte[] revision) throws IOException {
        //noinspection ConstantConditions
        filePath.getVirtualFile().setBinaryContent(revision);
    }

    private void writeContentToDocument(Project project, FilePath filePath, final Document document,
                                        byte[] revisionContent) throws IOException {
        final String content = StringUtil.convertLineSeparators(new String(revisionContent, filePath.getCharset().name()));

        CommandProcessor.getInstance().executeCommand(project, new Runnable() {
            public void run() {
                document.replaceString(0, document.getTextLength(), content);
            }
        }, "Get Version", null);
    }
}
